package nn.layers;

import java.util.ArrayList;

import nn.Neuron;
import nn.functions.ActivationFunction;
import nn.functions.ActivationFunctionFactory;
import nn.networks.KohonenNetwork;

import org.apache.commons.lang3.tuple.Pair;

public class Layer {

	public final int size;

	// protected Neuron[] neuronList;
	protected ActivationFunction actFunc;
	protected ArrayList<Neuron> neuronList;

	protected int neighborhoodSize;

	public int getNeighborhoodSize() {
		return neighborhoodSize;
	}

	public void setNeighborhoodSize(int neighborhoodSize) {
		this.neighborhoodSize = neighborhoodSize;
		setNeighbors(neighborhoodSize);
	}

	public Layer(int neuronCount, String actFunction) {
		size = neuronCount;
		neuronList = new ArrayList<Neuron>();
		actFunc = ActivationFunctionFactory.getFunction(actFunction);
		for (int it = 0; it < neuronCount; ++it) {
			neuronList.add(new Neuron());
		}
	}

	public Layer(int neuronNum) {
		this(neuronNum, ActivationFunctionFactory.LINEAR_FUNC);
	}

	public float[] getResults(float[] prevLayerResults) {
		float[] results = new float[size];
		// for (int it = 0; it < size; ++it) {
		// System.out.println(it+" : "
		// +neuronList[it].getRawValue(prevLayerResults)+" : "+(neuronList[it].getRawValue(prevLayerResults)
		// + neuronList[it].getBias()) +" : "+results[it]);
		// }
		if (size != neuronList.size()) {
			throw new RuntimeException("madafaka sie nie zgadza");
		}
		int it = 0;
		for (Neuron neuron : neuronList) {
			results[it] = actFunc.calculateValue(neuron.getRawValue(prevLayerResults) + neuron.getBias());
			++it;
		}
		return results;
	}

	public void setNeuronWeights(int neuronNum, float[] weights) {
		neuronList.get(neuronNum).setWeights(weights);
	}
	
	public void initNeuronWeights(int neuronNum, float[] weights) {
		neuronList.get(neuronNum).initNeuron(weights);
	}

	public ArrayList<Neuron> getNeuronList() {
		return neuronList;
	}

	public void setNeuronBias(int neuronNum, float bias) {
		neuronList.get(neuronNum).setBias(bias);

	}

	private void setNeighbors() {
		// 1D == -1
		if (this.neighborhoodSize == -1) {
			float oneDimDist = (float) 0.5;
			oneDimDist = KohonenNetwork.neuronsDistance(oneDimDist);
			for (Neuron it : neuronList) {
				if (neuronList.indexOf(it) == 0) {
					ArrayList<Pair<Neuron, Float>> result = new ArrayList<>();
					result.add(Pair.of(neuronList.get(neuronList.indexOf(it) + 1), oneDimDist));
					it.setNeighbours(result);
				} else if (neuronList.indexOf(it) == (neuronList.size() - 1)) {
					ArrayList<Pair<Neuron, Float>> result = new ArrayList<>();
					result.add(Pair.of((neuronList.get(neuronList.indexOf(it) - 1)), oneDimDist));
					it.setNeighbours(result);
				} else {
					ArrayList<Pair<Neuron, Float>> result = new ArrayList<>();
					result.add(Pair.of(neuronList.get(neuronList.indexOf(it) + 1), oneDimDist));
					result.add(Pair.of(neuronList.get(neuronList.indexOf(it) - 1), oneDimDist));
					it.setNeighbours(result);
				}
			}

		} else if (this.neighborhoodSize == 0) {

			for (Neuron it : neuronList) {
				it.setNeighbours(new ArrayList<Pair<Neuron, Float>>());
			}
			// diamon == -2
		} else if (this.neighborhoodSize == -2) {
			float diamondDist = (float) 0.7;
			diamondDist = KohonenNetwork.neuronsDistance(diamondDist);
			int neuronCount = neuronList.size();
			int squareSize = (int) Math.ceil(Math.sqrt(neuronCount));
			int count = 0;

			Neuron[][] board = new Neuron[squareSize][squareSize];

			for (int row = 0; row < squareSize; row++) {
				for (int col = 0; col < squareSize; col++) {
					if (count >= (neuronCount - 1)) {
						board[row][col] = null;
					} else {
						board[row][col] = neuronList.get(count);
						count++;
					}
				}
			}

			ArrayList<Pair<Neuron, Float>> resultList = new ArrayList<>();
			for (int row = 0; row < squareSize; row++) {
				for (int col = 0; col < squareSize; col++) {
					int neuronIndex = (squareSize * row) + col;

					if (neuronIndex < neuronCount) {
						if (col == 0 || row == 0) {
							if (board[1][0] != null) {
								resultList.add(Pair.of(board[1][0], diamondDist));
							}
							if (board[0][1] != null) {
								resultList.add(Pair.of(board[0][1], diamondDist));
							}
						} else if (col == (squareSize - 1) || row == 0) {
							if (board[squareSize - 2][0] != null) {
								resultList.add(Pair.of(board[squareSize - 2][0], diamondDist));
							}
							if (board[squareSize - 1][1] != null) {
								resultList.add(Pair.of(board[squareSize - 1][1], diamondDist));
							}

						} else if (col == 0 || row == (squareSize - 1)) {
							if (board[0][(squareSize - 2)] != null) {
								resultList.add(Pair.of(board[0][(squareSize - 2)], diamondDist));
							}
							if (board[squareSize - 1][1] != null) {
								resultList.add(Pair.of(board[squareSize - 1][1], diamondDist));
							}

						} else if (col == (squareSize - 1) || row == (squareSize - 1)) {
							if (board[(squareSize - 1)][(squareSize - 2)] != null) {
								resultList.add(Pair.of(board[(squareSize - 1)][(squareSize - 2)], diamondDist));
							}
							if (board[(squareSize - 2)][(squareSize - 1)] != null) {
								resultList.add(Pair.of(board[(squareSize - 2)][(squareSize - 1)], diamondDist));
							}
						} else if (row == 0) {
							if (board[col - 1][row] != null) {
								resultList.add(Pair.of(board[col - 1][row], diamondDist));
							}
							if (board[col + 1][row] != null) {
								resultList.add(Pair.of(board[col + 1][row], diamondDist));
							}
							if (board[col][row + 1] != null) {
								resultList.add(Pair.of(board[col][row + 1], diamondDist));
							}
						} else if (row == squareSize - 1) {
							if (board[col + 1][row] != null) {
								resultList.add(Pair.of(board[col + 1][row], diamondDist));
							}
							if (board[col - 1][row] != null) {
								resultList.add(Pair.of(board[col - 1][row], diamondDist));
							}
							if (board[col][squareSize - 2] != null) {
								resultList.add(Pair.of(board[col][squareSize - 2], diamondDist));
							}
						} else if (col == 0) {
							if (board[col][row - 1] != null) {
								resultList.add(Pair.of(board[col][row - 1], diamondDist));
							}
							if (board[col][row + 1] != null) {
								resultList.add(Pair.of(board[col][row + 1], diamondDist));
							}
							if (board[col + 1][row] != null) {
								resultList.add(Pair.of(board[col + 1][row], diamondDist));
							}
						} else if (col == squareSize - 1) {
							if (board[col][row + 1] != null) {
								resultList.add(Pair.of(board[col][row + 1], diamondDist));
							}
							if (board[col][row - 1] != null) {
								resultList.add(Pair.of(board[col][row - 1], diamondDist));
							}
							if (board[squareSize - 2][row] != null) {
								resultList.add(Pair.of(board[squareSize - 2][row], diamondDist));
							}
						} else {
							if (board[col][row + 1] != null) {
								resultList.add(Pair.of(board[col][row + 1], diamondDist));
							}
							if (board[col][row - 1] != null) {
								resultList.add(Pair.of(board[col][row - 1], diamondDist));
							}
							if (board[col + 1][row] != null) {
								resultList.add(Pair.of(board[col + 1][row], diamondDist));
							}
							if (board[col - 1][row] != null) {
								resultList.add(Pair.of(board[col - 1][row], diamondDist));
							}
						}
						neuronList.get(neuronIndex).addNewNeighbours(resultList);
						resultList.clear();
					}
				}
			}
		}
	}

	/**
	 * Sets the neighbours.
	 * 
	 * @param width
	 *            the new neighbours - 0 - no neigh, -1 - 1D, -2 - diamond (4
	 *            Neighbors), 1,2 etc , size of square containing neighbors
	 */
	public void setNeighbors(int width) {
		for (Neuron n : neuronList) {
			n.setNeighbours(new ArrayList<Pair<Neuron, Float>>());
		}
		int s = this.neighborhoodSize;
		if (s == 0 || s == -1 || s == -2) {
			setNeighbors();
			return;
		}
		int neuronCount = neuronList.size();
		int squareSize = (int) Math.ceil(Math.sqrt(neuronCount));
		int count = 0;
		Neuron[][] board = new Neuron[squareSize][squareSize];

		for (int row = 0; row < squareSize; row++) {
			for (int col = 0; col < squareSize; col++) {
				if (count >= (neuronCount - 1)) {
					board[row][col] = null;
				} else {
					board[row][col] = neuronList.get(count);
					count++;
				}
			}
		}
		for (int widthIt = 1; widthIt <= width; ++widthIt) {
			for (int row = 0; row < squareSize; row++) {
				for (int col = 0; col < squareSize; col++) {
					ArrayList<Pair<Neuron, Float>> resultList;
					int neuronIndex = (squareSize * row) + col;
					// left vertical (extra corner points)
					float dist = KohonenNetwork.neuronsDistance((float) width / (float) 2);
					resultList = putNeighboursLine(col - widthIt, col - widthIt, row - widthIt, row + widthIt, dist,
							squareSize, board);
					neuronList.get(neuronIndex).addNewNeighbours(resultList);
					// right vertical (extra corner points)
					resultList = putNeighboursLine(col + widthIt, col + widthIt, row - widthIt, row + widthIt, dist,
							squareSize, board);
					neuronList.get(neuronIndex).addNewNeighbours(resultList);
					// down horizontal
					resultList = putNeighboursLine(col - widthIt + 1, col + widthIt - 1, row - widthIt, row - widthIt,
							dist, squareSize, board);
					neuronList.get(neuronIndex).addNewNeighbours(resultList);
					// up horizontal
					resultList = putNeighboursLine(col - widthIt + 1, col + widthIt - 1, row + widthIt, row + widthIt,
							dist, squareSize, board);
					neuronList.get(neuronIndex).addNewNeighbours(resultList);
				}
			}
		}
	}

	private boolean isProperField(int x, int y, int squareSize, Neuron[][] board) {
		return x >= 0 && y >= 0 && x < squareSize && y < squareSize && board[x][y] != null;
	}

	// create neighbourd list from all neurons from board in given line. Line
	// position is passed by x/y min and max values. one of pair (x or y) must
	// be equal to give a line
	private ArrayList<Pair<Neuron, Float>> putNeighboursLine(int collMin, int collMax, int rowMin, int rowMax,
			float distance, int squareSize, Neuron[][] board) {
		if (collMin != collMax && rowMin != rowMax) {
			System.err.println("not a line");
			return null;
		}
		ArrayList<Pair<Neuron, Float>> resultList = new ArrayList<>();
		for (int coll = collMin; coll <= collMax; ++coll) {
			for (int row = rowMin; row <= rowMax; ++row) {
				if (isProperField(coll, row, squareSize, board)) {
					// coll row
					resultList.add(Pair.of(board[coll][row], (float) distance));
				}
			}
		}
		return resultList;
	}

}
